# coding=utf-8
import logging
import socket
import re
import traceback

from app.tasks import dump_bytes
from app.tasks.FrontServer import BaseRequestHandler, FrontServer


class FrontEndHandler(BaseRequestHandler):
    buffer = b''
    handlers = None
    logger = None

    matcher_storage = re.compile("^\\S+ \\S+ \\d+ -?\\d+ (\\d+)( noreply)?\\s*\r\n")
    matcher_cas = re.compile("^cas \\S+ \\d+ \\d+ (\\d+) [\\da-fA-F]+( noreply)?\r\n")
    matcher_retrieve = re.compile("^((get)|(gets)) \\S+.*\r\n")
    matcher_delete = re.compile("^delete \\S+( noreply)?\r\n")
    matcher_inc_dec = re.compile("^(incr)|(decr) \\S+ \\S+( noreply)?\r\n")
    matcher_touch = re.compile("^touch \\S+ \\d+( noreply)?\r\n")
    matcher_get_touch = re.compile("^(gat)|(gats) \\d+ (\\S+\r\n)|(\\S+ .*\r\n)")
    matcher_slabs_reassign = re.compile("^slabs reassign -?\\d+ \\d+\r\n")
    matcher_slabs_automove = re.compile("^slabs automove [0-5]\r\n")
    matcher_lru_tuning = re.compile("^lru ((tune)|(mode)|(temp_ttl)) .+\r\n")
    matcher_lru_crawler_enable_disable = re.compile("^lru_crawler ((enable)|(disable))\r\n")
    matcher_lru_crawler_sleep = re.compile("^lru_crawler sleep \\d+\r\n")
    matcher_lru_crawler_tocrawl = re.compile("^lru_crawler tocrawl \\d+\r\n")
    matcher_lru_crawler_crawl = re.compile("^lru_crawler crawl \\S+\r\n")
    matcher_lru_crawler_metadump = re.compile("^lru_crawler metadump \\S+\r\n")
    matcher_watch = re.compile("^watch ((fetchers)|(mutations)|(evictions))\r\n")

    logging_extra = {'backend': 'Front End'}

    def setup(self, as_active):
        self.logger = logging.getLogger("Proxy")
        if as_active:
            self.logger.warning("接受连接 '%s'" % str(self.client_address), extra=self.logging_extra)
            self.handlers = {
                # storage
                b"set ": self.handle_storages,
                b"add ": self.handle_storages,
                b"replace ": self.handle_storages,
                b"append ": self.handle_storages,
                b"prepend ": self.handle_storages,
                b"cas ": self.handle_cas,
                # retrieve
                b"get ": self.handle_retrieve,
                b"gets ": self.handle_retrieve,
                # deletion
                b"delete ": self.handle_delete,
                # increment/decremnt
                b"incr ": self.handle_increment_decrement,
                b"decr ": self.handle_increment_decrement,
                # touch
                b"touch ": self.handle_touch,
                # get and touch
                b"gat ": self.handle_get_touch,
                b"gats ": self.handle_get_touch,
                # Slabs Reassign
                b"slabs reassign ": self.handle_slabs_reassign,
                # Slabs Automove
                b"slabs automove ": self.handle_slabs_automove,
                # LRU Tuning
                b"lru ": self.handle_lru_tuning,
                # LRU_Crawler
                b"lru_crawler ": self.handle_lru_crawler,
                # Watchers
                b"watch ": self.handle_watch,
                # Statistics
                b"stats\r\n": self.handle_stats,
                b"stats ": self.handle_stats,
                b"version\r\n": self.handle_version,
                b"quit\r\n": self.handle_quit,
                b"misbehave\r\n": self.handle_misbehave
            }
        else:
            self.logger.warning("忽略连接'%s'，因为当前已存在一个有效连接" % str(self.client_address), extra=self.logging_extra)

    def teardown(self, as_active):
        if as_active:
            self.logger.warning("断开连接'%s'" % str(self.client_address), extra=self.logging_extra)
        super(FrontEndHandler, self).teardown(as_active)

    def handle(self):
        try:
            while self.request:
                try:
                    line = self.readline()
                    handler = None
                    for prefix, fn in self.handlers.items():
                        if line.startswith(prefix):
                            handler = fn
                            break
                    if handler:
                        data = handler(line)
                        if data:
                            # the request is valid
                            print("recv", dump_bytes(data))
                            self.server.proxy_app.write_to_backends(data)
                        else:
                            # the request is invalid
                            self.write(b"SERVER_ERROR the format of the command is NOT right\r\n")
                            self.logger.error("对于客户端的请求%s进行验证失败" % dump_bytes(line), extra=self.logging_extra)
                    else:
                        # the request can NOT be understand
                        if line == b'\r\n':
                            # skip
                            self.logger.warning("客户端发起了空白请求", extra=self.logging_extra)
                        else:
                            self.logger.critical("不支持的客户端请求%s" % dump_bytes(line), extra=self.logging_extra)
                            self.write(b"SERVER_ERROR unsupported command\r\n")
                except Exception as e:
                    if isinstance(e, socket.error):
                        if str(e) != 'disconnected by client':
                            self.logger.warning("网络异常：%s" % str(e), exc_info=True, extra=self.logging_extra)
                        break
                    else:
                        self.logger.error("未预料到的异常: %s" % str(e), exc_info=True, extra=self.logging_extra)
        except ConnectionAbortedError as e:
            pass
        except Exception as e:
            self.logger.critical("未预料到的异常: %s" % str(e), exc_info=True, extra=self.logging_extra)

    def handle_storages(self, line):
        m = self.matcher_storage.match(line.decode('utf-8'))
        if m is None:
            return None
        size = int(m.group(1)) + 2
        return line + self.readbytes(size)

    def handle_cas(self, line):
        m = self.matcher_cas.match(line.decode('utf-8'))
        if m is None:
            return None
        size = int(m.group(1)) + 2
        return line + self.readbytes(size)

    def handle_retrieve(self, line):
        m = self.matcher_retrieve.match(line.decode('utf-8'))
        if m is None:
            return None
        return line

    def handle_delete(self, line):
        m = self.matcher_delete.match(line.decode('utf-8'))
        if m is None:
            return None
        return line

    def handle_increment_decrement(self, line):
        m = self.matcher_inc_dec.match(line.decode('utf-8'))
        if m is None:
            return None
        return line

    def handle_touch(self, line):
        m = self.matcher_touch.match(line.decode('utf-8'))
        if m is None:
            return None
        return line

    def handle_get_touch(self, line):
        m = self.matcher_get_touch.match(line.decode('utf-8'))
        if m is None:
            return None
        return line

    def handle_slabs_reassign(self, line):
        m = self.matcher_slabs_reassign.match(line.decode('utf-8'))
        if m is None:
            return None
        return line

    def handle_slabs_automove(self, line):
        m = self.matcher_slabs_automove.match(line.decode('utf-8'))
        if m is None:
            return None
        return line

    def handle_lru_tuning(self, line):
        m = self.matcher_lru_tuning.match(line.decode('utf-8'))
        if m is None:
            return None
        return line

    def handle_lru_crawler(self, line):
        # lru_crawler <enable|disable>
        m = self.matcher_lru_crawler_enable_disable.match(line.decode('utf-8'))
        if m is not None:
            return line
        # lru_crawler sleep <microseconds>
        m = self.matcher_lru_crawler_sleep.match(line.decode('utf-8'))
        if m is None:
            return line
        # lru_crawler tocrawl <32u>
        m = self.matcher_lru_crawler_tocrawl.match(line.decode('utf-8'))
        if m is None:
            return line
        # lru_crawler crawl <classid,classid,classid|all>
        m = self.matcher_lru_crawler_crawl.match(line.decode('utf-8'))
        if m is None:
            return line
        # lru_crawler metadump <classid,classid,classid|all>
        m = self.matcher_lru_crawler_metadump.match(line.decode('utf-8'))
        if m is None:
            return line
        return line

    def handle_watch(self, line):
        m = self.matcher_watch.match(line.decode('utf-8'))
        if m is None:
            return None
        return line

    def handle_stats(self, line):
        # do not check stats because the command is tool complex
        return line


    def handle_version(self, line):
        # do not check stats because the command is tool simple
        return line

    def handle_quit(self, line):
        # do not check stats because the command is tool simple
        return line

    def handle_misbehave(self, line):
        # do not check stats because the command is tool simple
        return line

    def readline(self):
        while True:
            pos = self.buffer.find(b"\r\n")
            if pos >= 0:
                pos += 2
                data = self.buffer[:pos]
                self.buffer = self.buffer[pos:]
                return data
            else:
                data = self.request.recv(4096)
                if data:
                    self.buffer += data
                else:
                    raise socket.error("disconnected by client")

    def readbytes(self, size):
        while True:
            if len(self.buffer) >= size:
                data = self.buffer[:size]
                self.buffer = self.buffer[size:]
                return data
            else:
                data = self.request.recv(4096)
                if data:
                    self.buffer += data
                else:
                    raise socket.error("disconnected by client")

    def write(self, data):
        self.request.sendall(data)


def create_frontend(proxy, host_port):
    return FrontServer(proxy, host_port, FrontEndHandler)

# if __name__ == '__main__':
